EKS Auto Mode で独自の NodePool を作成して利用してみた
EKS Auto Mode にはノードを管理するための概念として、下記が存在します(正確にはマネージドな形で実行されている Karpenterの概念ですが)。
- NodeClass
- ネットワーク設定、ストレージ設定、リソースのタグ付けなど、AWS インフラレイヤーの詳細な設定を行う
- NodePool
- インスタンスタイプ、CPU アーキテクチャ (ARM64/AMD64)やキャパシティタイプ (スポット/オンデマンド)など、コンテナを起動するリソースの条件を指定する
- NodePool の中で NodeClass を指定して作成する
- NodeClaim
- NodeClass/NodePool を元に、実際に EKS ノードを作成してノードのライフサイクルを管理する
NodeClass と NodePool はデフォルトで用意されており、それを利用することも可能です。
ただし、スポットインスタンスを使いたい、GPU 系インスタンスを使いたいといった理由でユーザー側で作成することも可能です。
今回は実際に NodePool を作成して利用してみました。
デフォルトの NodeClass/NodePool を確認してみる
NodeClass はデフォルトで 1 つ存在します。
% kubectl describe nodeclass
Name: default
Namespace:
Labels: app.kubernetes.io/managed-by=eks
Annotations: eks.amazonaws.com/nodeclass-hash: 16389426616597791172
eks.amazonaws.com/nodeclass-hash-version: v1
API Version: eks.amazonaws.com/v1
Kind: NodeClass
Metadata:
Creation Timestamp: 2024-12-29T08:55:27Z
Finalizers:
eks.amazonaws.com/termination
Generation: 1
Resource Version: 1029735
UID: f5ab30b9-e11d-496a-983d-19e86dda727b
Spec:
Ephemeral Storage:
Iops: 3000
Size: 80Gi
Throughput: 125
Network Policy: DefaultAllow
Network Policy Event Logs: Disabled
Role: test-cluster-eks-auto-20241229084518842100000001
Security Group Selector Terms:
Id: sg-0995cc0a1e6144d0a
Snat Policy: Random
Subnet Selector Terms:
Id: subnet-06e81109a96d281a4
Id: subnet-07858fbd2ef50fe8a
Id: subnet-093c625d37faa0de0
Status:
Conditions:
Last Transition Time: 2024-12-29T08:56:13Z
Message:
Observed Generation: 1
Reason: SubnetsReady
Status: True
Type: SubnetsReady
Last Transition Time: 2024-12-29T08:56:13Z
Message:
Observed Generation: 1
Reason: SecurityGroupsReady
Status: True
Type: SecurityGroupsReady
Last Transition Time: 2024-12-29T08:56:13Z
Message:
Observed Generation: 1
Reason: InstanceProfileReady
Status: True
Type: InstanceProfileReady
Last Transition Time: 2024-12-29T08:56:13Z
Message:
Observed Generation: 1
Reason: Ready
Status: True
Type: Ready
Instance Profile: eks-ap-northeast-1-test-cluster-11975232971565303637
Security Groups:
Id: sg-0995cc0a1e6144d0a
Name: eks-cluster-sg-test-cluster-1656125505
Subnets:
Id: subnet-07858fbd2ef50fe8a
Zone: ap-northeast-1c
Zone ID: apne1-az1
Id: subnet-06e81109a96d281a4
Zone: ap-northeast-1d
Zone ID: apne1-az2
Id: subnet-093c625d37faa0de0
Zone: ap-northeast-1a
Zone ID: apne1-az4
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Finalized 31m (x17 over 10h) karpenter Finalized eks.amazonaws.com/termination
NodePool と比較すれば設定変更する機会は少ないと思いますが、エファメラルストレージを拡張したり、セキュリティグループを変更したりといった目的で新しく作成することができます。
また、NodePool はデフォルトで general-purpose と system が存在します。
generarl-purpose は一般的なコンテナアプリケーション用に作成されており、C 系、M 系、R 系のオンデマンドインスタンスを利用できます。
また、CPU アーキテクチャとしては amd64 が指定されています。
したがって、C 系、M 系、R 系以外の GPU などのインスタンスや、スポットインスタンス、ARM 系インスタンスなどを利用する場合は独自の NodePool を作成する必要があります。
% kubectl describe nodepool general-purpose
Name: general-purpose
Namespace:
Labels: app.kubernetes.io/managed-by=eks
Annotations: karpenter.sh/nodepool-hash: 4012513481623584108
karpenter.sh/nodepool-hash-version: v3
API Version: karpenter.sh/v1
Kind: NodePool
Metadata:
Creation Timestamp: 2024-12-29T08:55:27Z
Generation: 1
Resource Version: 28506
UID: 472bd24c-6606-4b3e-86dd-d45de8e35008
Spec:
Disruption:
Budgets:
Nodes: 10%
Consolidate After: 30s
Consolidation Policy: WhenEmptyOrUnderutilized
Template:
Metadata:
Spec:
Expire After: 336h
Node Class Ref:
Group: eks.amazonaws.com
Kind: NodeClass
Name: default
Requirements:
Key: karpenter.sh/capacity-type
Operator: In
Values:
on-demand
Key: eks.amazonaws.com/instance-category
Operator: In
Values:
c
m
r
Key: eks.amazonaws.com/instance-generation
Operator: Gt
Values:
4
Key: kubernetes.io/arch
Operator: In
Values:
amd64
Key: kubernetes.io/os
Operator: In
Values:
linux
Termination Grace Period: 24h0m0s
Status:
Conditions:
Last Transition Time: 2024-12-29T08:56:11Z
Message:
Observed Generation: 1
Reason: ValidationSucceeded
Status: True
Type: ValidationSucceeded
Last Transition Time: 2024-12-29T08:56:13Z
Message:
Observed Generation: 1
Reason: NodeClassReady
Status: True
Type: NodeClassReady
Last Transition Time: 2024-12-29T08:56:13Z
Message:
Observed Generation: 1
Reason: Ready
Status: True
Type: Ready
Resources:
Cpu: 0
Ephemeral - Storage: 0
Memory: 0
Nodes: 0
Pods: 0
Events: <none>
system と呼ばれる NodePool も存在していますが、こちらは CoreDNS などのクラスター運用に重要なリソースを配置するための NodePool になります。
Taint として CriticalAddonsOnly が設定されており、明示的に Toleration を設定した Pod 以外は配置できないようになっています。
% kubectl describe nodepool system
Name: system
Namespace:
Labels: app.kubernetes.io/managed-by=eks
Annotations: karpenter.sh/nodepool-hash: 4982684901400657622
karpenter.sh/nodepool-hash-version: v3
API Version: karpenter.sh/v1
Kind: NodePool
Metadata:
Creation Timestamp: 2024-12-31T14:13:29Z
Generation: 1
Resource Version: 1010214
UID: 4ed55c2d-7494-40b1-9e82-508030353d22
Spec:
Disruption:
Budgets:
Nodes: 10%
Consolidate After: 30s
Consolidation Policy: WhenEmptyOrUnderutilized
Template:
Metadata:
Spec:
Expire After: 336h
Node Class Ref:
Group: eks.amazonaws.com
Kind: NodeClass
Name: default
Requirements:
Key: karpenter.sh/capacity-type
Operator: In
Values:
on-demand
Key: eks.amazonaws.com/instance-category
Operator: In
Values:
c
m
r
Key: eks.amazonaws.com/instance-generation
Operator: Gt
Values:
4
Key: kubernetes.io/arch
Operator: In
Values:
amd64
arm64
Key: kubernetes.io/os
Operator: In
Values:
linux
Taints:
Effect: NoSchedule
Key: CriticalAddonsOnly
Termination Grace Period: 24h0m0s
Status:
Conditions:
Last Transition Time: 2024-12-31T14:13:29Z
Message:
Observed Generation: 1
Reason: NodeClassReady
Status: True
Type: NodeClassReady
Last Transition Time: 2024-12-31T14:13:29Z
Message:
Observed Generation: 1
Reason: ValidationSucceeded
Status: True
Type: ValidationSucceeded
Last Transition Time: 2024-12-31T14:13:29Z
Message:
Observed Generation: 1
Reason: Ready
Status: True
Type: Ready
Resources:
Cpu: 0
Ephemeral - Storage: 0
Memory: 0
Nodes: 0
Pods: 0
Events: <none>
下記のような、toleration 設定を行うことで初めて system の NodePool が利用されます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample-app
spec:
replicas: 3
selector:
matchLabels:
app: sample-app
template:
metadata:
labels:
app: sample-app
spec:
nodeSelector:
karpenter.sh/nodepool: system
tolerations:
- key: "CriticalAddonsOnly"
operator: "Exists"
containers:
- name: app
image: nginx:latest
resources:
requests:
cpu: "500m"
memory: "512Mi"
Run critical add-ons on dedicated instances
重要なリソースをアプリケーション用のリソースとは違うノードに配置したい場合に利用すると良いでしょう。
NodePool を作って利用してみる
今回は Spot インスタンスを利用したいケースを想定して、独自の NodePool を作成してみます。
Auto Mode を有効化した、EKSv1.31 で試していきます。
NodeClass は特に変更せず、default で用意されているものを指定します。
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
nodeClassRef:
group: eks.amazonaws.com
kind: NodeClass
name: default
requirements:
- key: "karpenter.sh/capacity-type"
operator: In
values: ["on-demand", "spot"]
- key: "eks.amazonaws.com/instance-category"
operator: In
values: ["c", "m", "r"]
- key: "eks.amazonaws.com/instance-cpu"
operator: In
values: ["2", "4", "8"]
- key: "kubernetes.io/arch"
operator: In
values: ["amd64"]
spec.requirements
として、利用する EC2 を指定します。
今回は karpenter.sh/capacity-type
として spot
を追加して、スポットインスタンスを利用できるようにします。
純粋な Karpenter の時とは微妙にキーが変わっているので、注意が必要です。
(例) karpenter.k8s.aws/instance-family
→ eks.amazonaws.com/instance-family
キーを指定する際は公式ドキュメントを確認することを推奨します。
ここを間違えると NodePool の Status に invalid value
としてメッセージが出るので、上手く動作しない場合は確認してみて下さい。
Status:
Conditions:
Last Transition Time: 2024-12-30T03:14:03Z
Message:
Observed Generation: 1
Reason: NodeClassReady
Status: True
Type: NodeClassReady
Last Transition Time: 2024-12-30T03:14:03Z
Message: invalid value: label karpenter.k8s.aws/instance-family is restricted; specify a well known label: [app.kubernetes.io/managed-by eks.amazonaws.com/compute-type eks.amazonaws.com/instance-accelerator-count eks.amazonaws.com/instance-accelerator-manufacturer eks.amazonaws.com/instance-accelerator-name eks.amazonaws.com/instance-category eks.amazonaws.com/instance-cpu eks.amazonaws.com/instance-cpu-manufacturer eks.amazonaws.com/instance-cpu-sustained-clock-speed-mhz eks.amazonaws.com/instance-ebs-bandwidth eks.amazonaws.com/instance-encryption-in-transit-supported eks.amazonaws.com/instance-family eks.amazonaws.com/instance-generation eks.amazonaws.com/instance-gpu-count eks.amazonaws.com/instance-gpu-manufacturer eks.amazonaws.com/instance-gpu-memory eks.amazonaws.com/instance-gpu-name eks.amazonaws.com/instance-hypervisor eks.amazonaws.com/instance-local-nvme eks.amazonaws.com/instance-memory eks.amazonaws.com/instance-network-bandwidth eks.amazonaws.com/instance-size karpenter.sh/capacity-type karpenter.sh/nodepool kubernetes.io/arch kubernetes.io/os node.kubernetes.io/instance-type node.kubernetes.io/windows-build topology.k8s.aws/zone-id topology.kubernetes.io/region topology.kubernetes.io/zone], or a custom label that does not use a restricted domain: [k8s.io karpenter.k8s.aws karpenter.sh kubernetes.io] in requirements, restricted
上記マニフェストファイルを適用すると、Status としても Ready になりました。
% kubectl describe nodepool default
Name: default
Namespace:
Labels: <none>
Annotations: karpenter.sh/nodepool-hash: 4012513481623584108
karpenter.sh/nodepool-hash-version: v3
API Version: karpenter.sh/v1
Kind: NodePool
Metadata:
Creation Timestamp: 2024-12-31T14:16:00Z
Generation: 4
Resource Version: 1046049
UID: d3c2a42d-ba3e-4d50-8856-d2d8bb4e9562
Spec:
Disruption:
Budgets:
Nodes: 10%
Consolidate After: 0s
Consolidation Policy: WhenEmptyOrUnderutilized
Template:
Spec:
Expire After: 336h
Node Class Ref:
Group: eks.amazonaws.com
Kind: NodeClass
Name: default
Requirements:
Key: karpenter.sh/capacity-type
Operator: In
Values:
on-demand
spot
Key: eks.amazonaws.com/instance-category
Operator: In
Values:
c
m
r
Key: eks.amazonaws.com/instance-cpu
Operator: In
Values:
2
4
8
Key: kubernetes.io/arch
Operator: In
Values:
amd64
Termination Grace Period: 24h
Status:
Conditions:
Last Transition Time: 2024-12-31T14:16:00Z
Message:
Observed Generation: 4
Reason: ValidationSucceeded
Status: True
Type: ValidationSucceeded
Last Transition Time: 2024-12-31T14:16:00Z
Message:
Observed Generation: 4
Reason: NodeClassReady
Status: True
Type: NodeClassReady
Last Transition Time: 2024-12-31T16:05:10Z
Message:
Observed Generation: 4
Reason: Ready
Status: True
Type: Ready
Resources:
Cpu: 0
Ephemeral - Storage: 0
Memory: 0
Nodes: 0
Pods: 0
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Ready 65s (x4 over 110m) karpenter Status condition transitioned, Type: Ready, Status: Unknown -> True, Reason: Ready
Normal Ready 65s (x2 over 105m) karpenter Status condition transitioned, Type: Ready, Status: True -> Unknown, Reason: ReconcilingDependents, Message: NodeClassReady=True
NodeSelector でスポットインスタンスの利用を指定して、Nginx コンテナを起動してみます。
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: default
name: nginx
spec:
selector:
matchLabels:
app.kubernetes.io/name: nginx
replicas: 2
template:
metadata:
labels:
app.kubernetes.io/name: nginx
spec:
nodeSelector:
karpenter.sh/nodepool: default
karpenter.sh/capacity-type: spot
containers:
- image: nginx:1.14.2
imagePullPolicy: Always
name: nginx
ports:
- containerPort: 80
resources:
requests:
cpu: "0.5"
NodePool として作成した default を指定しつつ、スポットインスタンスをリクエストしています。
明示的に NodePool を指定しなくても条件に応じて最適な NodePool が選択されるため、NodePool の指定は必須ではありません。
複数の NodePool が条件を満たす際に動作が分かり辛くなりそうだったので、今回は明示的に指定しています。
利用しない組み込みの NodePool は EKS の設定で無効化できるので、必要に応じて設定変更すると良いかと思います。
しばらくして、無事 Pod が作成されてました(下記コマンドの実行タイミングが遅れただけで 1 分弱でノードが作成されます)。
% kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-85c5b79bd8-j4k2r 1/1 Running 0 3m19s
nginx-85c5b79bd8-t2c9j 1/1 Running 0 3m19s
実際に EKS ノードを作成する NodeClaim も作成されています。
NodePool の段階ではオンデマンド or スポットなど、ある程度幅のある指定でしたが、NodeClaim が作成されるタイミングでは各設定値が決定されています。
karpenter.sh/nodepool=default
と書いてあるので、作成した NodePool がちゃんと利用されています。
% kubectl describe nodeclaim
Name: default-xngjq
Namespace:
Labels: app.kubernetes.io/managed-by=eks
eks.amazonaws.com/compute-type=auto
eks.amazonaws.com/instance-category=c
eks.amazonaws.com/instance-cpu=2
eks.amazonaws.com/instance-cpu-manufacturer=amd
eks.amazonaws.com/instance-cpu-sustained-clock-speed-mhz=3300
eks.amazonaws.com/instance-ebs-bandwidth=3170
eks.amazonaws.com/instance-encryption-in-transit-supported=true
eks.amazonaws.com/instance-family=c5a
eks.amazonaws.com/instance-generation=5
eks.amazonaws.com/instance-hypervisor=nitro
eks.amazonaws.com/instance-memory=4096
eks.amazonaws.com/instance-network-bandwidth=750
eks.amazonaws.com/instance-size=large
eks.amazonaws.com/nodeclass=default
karpenter.sh/capacity-type=spot
karpenter.sh/nodepool=default
kubernetes.io/arch=amd64
kubernetes.io/os=linux
node.kubernetes.io/instance-type=c5a.large
topology.k8s.aws/zone-id=apne1-az1
topology.kubernetes.io/region=ap-northeast-1
topology.kubernetes.io/zone=ap-northeast-1c
Annotations: eks.amazonaws.com/nodeclass-hash: 16389426616597791172
eks.amazonaws.com/nodeclass-hash-version: v1
karpenter.sh/nodepool-hash: 4012513481623584108
karpenter.sh/nodepool-hash-version: v3
API Version: karpenter.sh/v1
Kind: NodeClaim
Metadata:
Creation Timestamp: 2024-12-31T16:13:48Z
Finalizers:
karpenter.sh/termination
Generate Name: default-
Generation: 1
Owner References:
API Version: karpenter.sh/v1
Block Owner Deletion: true
Kind: NodePool
Name: default
UID: d3c2a42d-ba3e-4d50-8856-d2d8bb4e9562
Resource Version: 1048982
UID: 3a993ddb-27d2-4a34-bfee-88778914e710
Spec:
Expire After: 336h
Node Class Ref:
Group: eks.amazonaws.com
Kind: NodeClass
Name: default
Requirements:
Key: node.kubernetes.io/instance-type
Operator: In
Values:
c4.large
c4.xlarge
c5.large
c5.xlarge
c5a.large
c5a.xlarge
c5d.large
c5n.large
c6a.large
c6a.xlarge
c6i.large
c6i.xlarge
c6id.large
c6id.xlarge
c6in.large
c6in.xlarge
c7a.large
c7a.xlarge
c7i-flex.large
c7i-flex.xlarge
c7i.large
c7i.xlarge
m4.large
m4.xlarge
m5.large
m5.xlarge
m5a.large
m5ad.large
m5d.large
m5dn.large
m5n.large
m5zn.large
m6a.large
m6a.xlarge
m6i.large
m6i.xlarge
m6id.large
m6idn.large
m6in.large
m7a.large
m7i-flex.large
m7i-flex.xlarge
m7i.large
r4.large
r5.large
r5a.large
r5ad.large
r5b.large
r5d.large
r5dn.large
r5n.large
r6a.large
r6a.xlarge
r6i.large
r6id.large
r6idn.large
r6in.large
r7a.large
r7i.large
r7iz.large
Key: eks.amazonaws.com/instance-cpu
Operator: In
Values:
2
4
8
Key: karpenter.sh/nodepool
Operator: In
Values:
default
Key: eks.amazonaws.com/nodeclass
Operator: In
Values:
default
Key: kubernetes.io/arch
Operator: In
Values:
amd64
Key: karpenter.sh/capacity-type
Operator: In
Values:
spot
Key: eks.amazonaws.com/instance-category
Operator: In
Values:
c
m
r
Resources:
Requests:
Cpu: 1
Pods: 2
Termination Grace Period: 24h0m0s
Status:
Allocatable:
Cpu: 1480m
Ephemeral - Storage: 71Gi
Memory: 2699Mi
Pods: 27
Capacity:
Cpu: 2
Ephemeral - Storage: 80Gi
Memory: 3788Mi
Pods: 27
Conditions:
Last Transition Time: 2024-12-31T16:13:51Z
Message:
Observed Generation: 1
Reason: Launched
Status: True
Type: Launched
Last Transition Time: 2024-12-31T16:14:05Z
Message:
Observed Generation: 1
Reason: Registered
Status: True
Type: Registered
Last Transition Time: 2024-12-31T16:14:07Z
Message:
Observed Generation: 1
Reason: Initialized
Status: True
Type: Initialized
Last Transition Time: 2024-12-31T16:14:07Z
Message:
Observed Generation: 1
Reason: Consolidatable
Status: True
Type: Consolidatable
Last Transition Time: 2024-12-31T16:14:07Z
Message:
Observed Generation: 1
Reason: Ready
Status: True
Type: Ready
Image ID: ami-0a6f527d62b57d7ad
Last Pod Event Time: 2024-12-31T16:14:12Z
Node Name: i-07a9c99a235f1d699
Provider ID: aws:///ap-northeast-1c/i-07a9c99a235f1d699
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Launched 4m25s karpenter Status condition transitioned, Type: Launched, Status: Unknown -> True, Reason: Launched
Normal DisruptionBlocked 4m20s karpenter Cannot disrupt NodeClaim: nodeclaim does not have an associated node
Normal Registered 4m10s karpenter Status condition transitioned, Type: Registered, Status: Unknown -> True, Reason: Registered
Normal Initialized 4m9s karpenter Status condition transitioned, Type: Initialized, Status: Unknown -> True, Reason: Initialized
Normal Ready 4m9s karpenter Status condition transitioned, Type: Ready, Status: Unknown -> True, Reason: Ready
Normal Unconsolidatable 3m39s karpenter Can't replace with a cheaper node
c5a.large のスポットインスタンスがリクエストされてました。
まとめ
今回は独自の NodePool を作成して利用してみました。
同様の方法で GPU 系インスタンスや、Arm 系のインスタンスも利用できるはずです。
一つのクラスターで簡単にスポットインスタンス、GPU 系インスタンス、Arm 系インスタンスなどを使い分けることができるのは EKS Auto Mode の大きな強みだと思います。
特に GPU 利用については、EKS Auto Mode を使う強力なモチベーションになると思うので別の機会に試してみます。